package com.puzzlebazar.client.squaregrid.control;

import java.util.Iterator;

import com.google.gwt.event.dom.client.HasAllMouseHandlers;
import com.google.gwt.event.dom.client.MouseDownEvent;
import com.google.gwt.event.dom.client.MouseDownHandler;
import com.google.gwt.event.dom.client.MouseEvent;
import com.google.gwt.event.dom.client.MouseMoveEvent;
import com.google.gwt.event.dom.client.MouseMoveHandler;
import com.google.gwt.event.dom.client.MouseOutEvent;
import com.google.gwt.event.dom.client.MouseOutHandler;
import com.google.gwt.event.dom.client.MouseOverEvent;
import com.google.gwt.event.dom.client.MouseOverHandler;
import com.google.gwt.event.dom.client.MouseUpEvent;
import com.google.gwt.event.dom.client.MouseUpHandler;
import com.google.gwt.event.dom.client.MouseWheelEvent;
import com.google.gwt.event.dom.client.MouseWheelHandler;
import com.google.gwt.event.shared.EventHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.Event.NativePreviewEvent;
import com.google.gwt.user.client.Event.NativePreviewHandler;
import com.google.gwt.user.client.ui.SimplePanel;
import com.puzzlebazar.client.squaregrid.view.CompositeView;
import com.puzzlebazar.client.squaregrid.view.SGView;
import com.puzzlebazar.client.util.ConcurrentVector;
import com.puzzlebazar.client.util.GWTUtil;
import com.puzzlebazar.client.util.Recti;
import com.puzzlebazar.client.util.Vec2i;

/**
 * Adds a decorator that is able to manage mouse clicking and movement within a grid at a low
 * level, but that tracks everything in terms of vertex location, edge location and cell locations.
 * The user can attach callbacks to cell, edges and vertex.
 * 
 * Vertex events always have priority. If they are not handled, then edge events will be checked for. 
 * Only one edge event (vertical or horizontal) can occur. If the edge event is not handled then cell 
 * events will be checked for.
 * 
 * Note that all the MouseLeave events occur before any new MouseEnter.
 * 
 * @author beaudoin
 */
public class ControlView extends CompositeView {

	private final ControlPanel panel = new ControlPanel();

	private final ConcurrentVector< ComponentCallback > cellCallbacks = new ConcurrentVector< ComponentCallback >();
	private final ConcurrentVector< ComponentCallback > verticalEdgeCallbacks = new ConcurrentVector< ComponentCallback >();
	private final ConcurrentVector< ComponentCallback > horizontalEdgeCallbacks = new ConcurrentVector< ComponentCallback >();
	private final ConcurrentVector< ComponentCallback > vertexCallbacks = new ConcurrentVector< ComponentCallback >();
	
	private final LocInfo lastLocInfo = new LocInfo();
	private final MouseDownCallbacks mouseDownCallbacks = new MouseDownCallbacks();
	private final MouseUpCallbacks mouseUpCallbacks = new MouseUpCallbacks();
	
	
	private float relativeVertexSize = 0.1f;
	
	private float relativeEdgeSize = 0.1f;
	
	private class LocInfo {	
		private final Vec2i vertexLoc = new Vec2i(-1,-1);
		private final Vec2i cellLoc = new Vec2i(-1,-1);
		private final Vec2i vEdgeLoc = new Vec2i(-1,-1);
		private final Vec2i hEdgeLoc = new Vec2i(-1,-1);
	
		public void setLocations( Vec2i vertexLoc, Vec2i cellLoc ) {
			this.vertexLoc.copy(vertexLoc);
			this.cellLoc.copy(cellLoc);
			vEdgeLoc.x = vertexLoc.x;
			vEdgeLoc.y = cellLoc.y;
			hEdgeLoc.x = cellLoc.x;
			hEdgeLoc.y = vertexLoc.y;
	
		}
		
		public final Vec2i getVertexLoc() {
			return vertexLoc;
		}
		
		public final Vec2i getVerticalEdgeLoc() {
			return vEdgeLoc;
		}
		
		public final Vec2i getHorizontalEdgeLoc() {
			return hEdgeLoc;
		}
		
		public final Vec2i getCellLoc() {
			return cellLoc;
		}
		
		public void invalidateVertexLoc() {
			vertexLoc.x = -1;
		}
		
		public void invalidateVerticalEdgeLoc() {
			vEdgeLoc.x = -1;
		}
	
		public void invalidateHorizontalEdgeLoc() {
			hEdgeLoc.x = -1;
		}
	
		public void invalidateCellLoc() {
			cellLoc.x = -1;
		}
		
		public void invalidateAll() {
			invalidateVertexLoc();
			invalidateVerticalEdgeLoc();
			invalidateHorizontalEdgeLoc();
			invalidateCellLoc();
		}
		
		public boolean isVertexLocValid() {
			return vertexLoc.x != -1;
		}
		
		public boolean isVerticalEdgeLocValid() {
			return vEdgeLoc.x != -1;
		}
	
		public boolean isHorizontalEdgeLocValid() {
			return hEdgeLoc.x != -1;
		}
		
		public boolean isCellLocValid() {
			return cellLoc.x != -1;
		}
	
		public void copy(LocInfo locInfo) {
			vertexLoc.copy( locInfo.vertexLoc );
			vEdgeLoc.copy( locInfo.vEdgeLoc );
			hEdgeLoc.copy( locInfo.hEdgeLoc );
			cellLoc.copy( locInfo.cellLoc );
		}
		
//		public void clear() {
//			invalidateVertexLoc();
//			invalidateVerticalEdgeLoc();
//			invalidateHorizontalEdgeLoc();
//			invalidateCellLoc();
//		}
	}

	private static class MultiHandler { 

		public static boolean mouseDown( ConcurrentVector< ComponentCallback > collection, Vec2i loc, MouseEvent<? extends EventHandler> event ) {
			boolean handled = false;
			for (Iterator<ComponentCallback> iterator = collection.iterator(); iterator.hasNext();) {
				ComponentCallback callback = iterator.next();
				if( callback.mouseDown( loc, event ) ) handled = true;
			}
			return handled;
		}

		public static boolean mouseUp( ConcurrentVector< ComponentCallback > collection, Vec2i loc, MouseEvent<? extends EventHandler> event  ) {
			boolean handled = false;
			for (Iterator<ComponentCallback> iterator = collection.iterator(); iterator.hasNext();) {
				ComponentCallback callback = iterator.next();
				if( callback.mouseUp( loc, event ) ) handled = true;
			}
			return handled;
		}

		public static boolean canHandleMouseEnter( ConcurrentVector< ComponentCallback > collection, Vec2i loc, MouseEvent<? extends EventHandler> event  ) {
			for (Iterator<ComponentCallback> iterator = collection.iterator(); iterator.hasNext();) {
				ComponentCallback callback = iterator.next();
				if( callback.canHandleMouseEnter( loc, event ) ) return true;
			}
			return false;
		}

		public static boolean mouseEnter( ConcurrentVector< ComponentCallback > collection, Vec2i loc, MouseEvent<? extends EventHandler> event  ) {
			boolean handled = false;
			for (Iterator<ComponentCallback> iterator = collection.iterator(); iterator.hasNext();) {
				ComponentCallback callback = iterator.next();
				if( callback.mouseEnter( loc, event ) ) handled = true;
			}
			return handled;
		}
		

		public static boolean mouseLeave( ConcurrentVector< ComponentCallback > collection, Vec2i loc, MouseEvent<? extends EventHandler> event  ) {
			boolean handled = false;
			for (Iterator<ComponentCallback> iterator = collection.iterator(); iterator.hasNext();) {
				ComponentCallback callback = iterator.next();
				if( callback.mouseLeave( loc, event ) ) handled = true;
			}
			return handled;
		}
			
	}

	
	private interface CallbackGroup {
		
		public boolean handleVertex( Vec2i vertexLoc, MouseEvent<? extends EventHandler> event  );
		public boolean handleVerticalEdge( Vec2i edgeLoc, MouseEvent<? extends EventHandler> event  );
		public boolean handleHorizontalEdge( Vec2i edgeLoc, MouseEvent<? extends EventHandler> event  );
		public boolean handleCell( Vec2i cellLoc, MouseEvent<? extends EventHandler> event );
	}

	private class MouseDownCallbacks implements CallbackGroup {
		
		@Override
		public boolean handleVertex(Vec2i vertexLoc, MouseEvent<? extends EventHandler> event ) {
			return MultiHandler.mouseDown( vertexCallbacks, vertexLoc, event );
		}

		@Override
		public boolean handleVerticalEdge(Vec2i edgeLoc, MouseEvent<? extends EventHandler> event ) {
			return MultiHandler.mouseDown( verticalEdgeCallbacks, edgeLoc, event );
		}

		@Override
		public boolean handleHorizontalEdge(Vec2i edgeLoc, MouseEvent<? extends EventHandler> event ) {
			return MultiHandler.mouseDown( horizontalEdgeCallbacks, edgeLoc, event );
		}

		@Override
		public boolean handleCell(Vec2i cellLoc, MouseEvent<? extends EventHandler> event ) {
			return MultiHandler.mouseDown( cellCallbacks, cellLoc, event );
		}

	}	
	
	private class MouseUpCallbacks implements CallbackGroup {
		
		@Override
		public boolean handleVertex(Vec2i vertexLoc, MouseEvent<? extends EventHandler> event) {
			return MultiHandler.mouseUp( vertexCallbacks, vertexLoc, event );
		}

		@Override
		public boolean handleVerticalEdge(Vec2i edgeLoc, MouseEvent<? extends EventHandler> event) {
			return MultiHandler.mouseUp( verticalEdgeCallbacks, edgeLoc, event );
		}

		@Override
		public boolean handleHorizontalEdge(Vec2i edgeLoc, MouseEvent<? extends EventHandler> event) {
			return MultiHandler.mouseUp( horizontalEdgeCallbacks, edgeLoc, event );
		}

		@Override
		public boolean handleCell(Vec2i cellLoc, MouseEvent<? extends EventHandler> event) {
			return MultiHandler.mouseUp( cellCallbacks, cellLoc, event );
		}

	}
	
	private class ControlPanel
	  extends SimplePanel
	  implements HasAllMouseHandlers, NativePreviewHandler {
	   		
		/**
		 * Creates a new panel that can be used for control
		 * 
		 * @param inner Content
		 */
		public ControlPanel() {
			setStyleName("controlPanel");
			DOM.setStyleAttribute(getElement(), "position", "absolute");
		    DOM.sinkEvents(getElement(),
		    		DOM.getEventsSunk(getElement()) | Event.MOUSEEVENTS );
		    Event.addNativePreviewHandler(this);
		    GWTUtil.disableContextMenu( getElement() );
		    ControlPanelHandler controlPanelHandler = new ControlPanelHandler();
		    addMouseDownHandler(controlPanelHandler);
		    addMouseUpHandler(controlPanelHandler);
		    addMouseMoveHandler(controlPanelHandler);
		    addMouseOutHandler(controlPanelHandler);
		}
		

		@Override
		public HandlerRegistration addMouseDownHandler(MouseDownHandler handler) {
			return addDomHandler(handler, MouseDownEvent.getType());
		}
		
		@Override
		public HandlerRegistration addMouseUpHandler(MouseUpHandler handler) {
			return addDomHandler(handler, MouseUpEvent.getType());
		}
		
		@Override
		public HandlerRegistration addMouseOutHandler(MouseOutHandler handler) {
			return addDomHandler(handler, MouseOutEvent.getType());
		}
		
		@Override
		public HandlerRegistration addMouseOverHandler(MouseOverHandler handler) {
			return addDomHandler(handler, MouseOverEvent.getType());
		}
		
		@Override
		public HandlerRegistration addMouseMoveHandler(MouseMoveHandler handler) {
			return addDomHandler(handler, MouseMoveEvent.getType());
		}

		@Override
		public HandlerRegistration addMouseWheelHandler(MouseWheelHandler handler) {
			return addDomHandler(handler, MouseWheelEvent.getType());
		}
		
		
		@Override
		public void onPreviewNativeEvent(NativePreviewEvent nativeEvent) {
			Event event = Event.as( nativeEvent.getNativeEvent() );
			if (DOM.eventGetType(event) == Event.ONMOUSEDOWN &&
					DOM.isOrHasChild(getElement(), DOM.eventGetCurrentTarget(event))) {
				DOM.eventPreventDefault(event);
			}
			
		};		
		
		
		private class ControlPanelHandler implements MouseDownHandler,
			MouseUpHandler, MouseMoveHandler, MouseOutHandler{

			@Override
			public void onMouseDown(MouseDownEvent event) {
				DOM.setCapture( getElement() );
				handleUpDownEvent( mouseDownCallbacks, event );				
			}

			@Override
			public void onMouseUp(MouseUpEvent event) {
				handleUpDownEvent( mouseUpCallbacks, event );
				DOM.releaseCapture( getElement() );
			}

			@Override
			public void onMouseMove(MouseMoveEvent event) {
				handleMoveEvent( event );
			}

			@Override
			public void onMouseOut(MouseOutEvent event) {
				leaveAll( event );
			}
		}



		
	}
	
	
	/**
	 * Generates a decorator that is able to add control for 
	 * clicking on a square grid
	 * 
	 * @throws Exception
	 */
	public ControlView() throws Exception {
		super();
		lastLocInfo.invalidateAll();
		
		initWidget( panel );
	}
		
	/**
	 * Handles a mouse up or down event. Note that this method will not update this.lastLoc, 
	 * the last location is only updated when calling handleMoveEvent;
	 * 
	 * @param callbackGroup The group of callback methods to use (either MouseDownCallbacks or MouseUpCallbacks)
	 * @param event The current event of the mouse
	 */
	private void handleUpDownEvent( final CallbackGroup callbackGroup, final MouseEvent<? extends EventHandler> event) {
		
		try {
			LocInfo locInfo = fillLocInfo( new Vec2i(event.getX(),event.getY()) );
			
			boolean handled = false;
			if( vertexCallbacks != null && locInfo.isVertexLocValid() )
				handled = callbackGroup.handleVertex(locInfo.getVertexLoc(), event);
			if( !handled && verticalEdgeCallbacks != null && locInfo.isVerticalEdgeLocValid() )
				handled = callbackGroup.handleVerticalEdge(locInfo.getVerticalEdgeLoc(), event);
			if( !handled && horizontalEdgeCallbacks != null && locInfo.isHorizontalEdgeLocValid() )
				handled = callbackGroup.handleHorizontalEdge(locInfo.getHorizontalEdgeLoc(), event);
			if( !handled && cellCallbacks != null && locInfo.isCellLocValid() )
				handled = callbackGroup.handleCell(locInfo.getCellLoc(), event);
		}
		catch ( Exception exception ) {
			exception.printStackTrace();
		}
	}
		
	/**
	 * Handles a move event within the controller panel and update the last
	 * known mouse position
	 * 
	 * @param event The current position of the move.
	 */
	private void handleMoveEvent( MouseMoveEvent event ) {
		
		handleMoveEventNoUpdate(event);
	}

	/**
	 * Handles a move event within the controller panel. Will generate MouseEnter 
	 * and MouseLeave events for various components (Vertex, Edge or Cell). Vertex-related
	 * events have priority over Edge-related events which have priority over Cell-related
	 * events. If an event is not handled by the client then the next, lower priority event 
	 * is triggered.
	 * 
	 * @param event The current event.
	 */
	private void handleMoveEventNoUpdate(MouseEvent<? extends EventHandler> event) {
		try {
			LocInfo locInfo = fillLocInfo( new Vec2i(event.getX(), event.getY()) );
			handleVertexEnterEvent( locInfo, event );
			handleVerticalEdgeEnterEvent( locInfo, event );
			handleHorizontalEdgeEnterEvent( locInfo, event );
			handleCellEnterEvent( locInfo, event );	

			handleLeaveEvents( locInfo, event );
			handleEnterEvents( locInfo, event );
			lastLocInfo.copy( locInfo );
		}
		catch ( Exception exception ) {
			exception.printStackTrace();
		}
	}
	
	/**
	 * Generate events to leave all components (Vertex, Edge or Cell)
	 * 
	 * @param event The event that generated this call
	 */
	private void leaveAll( MouseEvent<? extends EventHandler> event ) {
		LocInfo locInfo = new LocInfo();
		assert( !locInfo.isVertexLocValid() );
		assert( !locInfo.isVerticalEdgeLocValid() );
		assert( !locInfo.isHorizontalEdgeLocValid() );
		assert( !locInfo.isCellLocValid() );
		handleLeaveEvents( locInfo, event );
		lastLocInfo.copy( locInfo );
	}
	
	/**
	 * Compares the passed locInfo to lastLocInfo and sends MouseLeave event to any
	 * component (Vertex, Edge or Cell) that was valid before but is no longer valid,
	 * or whose location has changed since last time.
	 * 
	 * @param locInfo The loc info containing valid entries and their location
	 * @param event The event that generated this call
	 */
	private void handleLeaveEvents(LocInfo locInfo, MouseEvent<? extends EventHandler> event ) {

		if( vertexCallbacks != null ) {
			if( lastLocInfo.isVertexLocValid() ) {
				final Vec2i vertexLoc = lastLocInfo.getVertexLoc();
				if ( !locInfo.isVertexLocValid() || !vertexLoc.equals( locInfo.getVertexLoc() ) )
					MultiHandler.mouseLeave( vertexCallbacks, vertexLoc, event );
			}
		}
				
		if( verticalEdgeCallbacks != null ) {
			if( lastLocInfo.isVerticalEdgeLocValid() ) {
				final Vec2i edgeLoc = lastLocInfo.getVerticalEdgeLoc();
				if( !locInfo.isVerticalEdgeLocValid() || !edgeLoc.equals( locInfo.getVerticalEdgeLoc() ) )
					MultiHandler.mouseLeave( verticalEdgeCallbacks, edgeLoc, event );
			}
		}
		
		if( horizontalEdgeCallbacks != null ) {
			if( lastLocInfo.isHorizontalEdgeLocValid() ) {
				final Vec2i edgeLoc = lastLocInfo.getHorizontalEdgeLoc();
				if( !locInfo.isHorizontalEdgeLocValid() || !edgeLoc.equals( locInfo.getHorizontalEdgeLoc() ) )
					MultiHandler.mouseLeave( horizontalEdgeCallbacks, edgeLoc, event );
			}
		}

		if( cellCallbacks != null ) {
			if( lastLocInfo.isCellLocValid() ) {
				final Vec2i cellLoc = lastLocInfo.getCellLoc();
				if ( !locInfo.isCellLocValid() || !cellLoc.equals( locInfo.getCellLoc() ) )
					MultiHandler.mouseLeave( cellCallbacks, cellLoc, event );
			}
		}
	}

	/**
	 * Compares the passed locInfo to lastLocInfo and sends MouseEnter event to any
	 * component (Vertex, Edge or Cell) that was invalid before and is valid now,
	 * or whose location has changed since last time.
	 * 
	 * @param locInfo The loc info containing valid entries and their location
	 * @param event The event that generated this call
	 */
	private void handleEnterEvents(LocInfo locInfo, MouseEvent<? extends EventHandler> event) {

		if( vertexCallbacks != null ) {
			if( locInfo.isVertexLocValid() ) {
				final Vec2i vertexLoc = locInfo.getVertexLoc();
				if ( !lastLocInfo.isVertexLocValid() || !vertexLoc.equals( lastLocInfo.getVertexLoc() ) )
					MultiHandler.mouseEnter( vertexCallbacks, vertexLoc, event );
			}
		}
				
		if( verticalEdgeCallbacks != null ) {
			if( locInfo.isVerticalEdgeLocValid() ) {
				final Vec2i edgeLoc = locInfo.getVerticalEdgeLoc();
				if( !lastLocInfo.isVerticalEdgeLocValid() || !edgeLoc.equals( lastLocInfo.getVerticalEdgeLoc() ) )
					MultiHandler.mouseEnter( verticalEdgeCallbacks, edgeLoc, event );
			}
		}
		
		if( horizontalEdgeCallbacks != null ) {
			if( locInfo.isHorizontalEdgeLocValid() ) {
				final Vec2i edgeLoc = locInfo.getHorizontalEdgeLoc();
				if( !lastLocInfo.isHorizontalEdgeLocValid() || !edgeLoc.equals( lastLocInfo.getHorizontalEdgeLoc() ) )
					MultiHandler.mouseEnter( horizontalEdgeCallbacks, edgeLoc, event );
			}
		}

		if( cellCallbacks != null ) {
			if( locInfo.isCellLocValid() ) {
				final Vec2i cellLoc = locInfo.getCellLoc();
				if ( !lastLocInfo.isCellLocValid() || !cellLoc.equals( lastLocInfo.getCellLoc() ) )
					MultiHandler.mouseEnter( cellCallbacks, cellLoc, event );
			}
		}
	}

	/**
	 * Tries to handle a VertexEnter event. If the event is successfully handled, then
	 * the function modifies the passed locInfo object to invalidate all other locations, 
	 * making sure no further event will be handled.
	 * 
	 * @param locInfo The locInfo generate from the mouse position
	 * @param event The event that generated this call
	 */
	private void handleVertexEnterEvent(LocInfo locInfo, MouseEvent<? extends EventHandler> event) {

		boolean handled = false;
		
		if( vertexCallbacks != null && locInfo.isVertexLocValid() ) {
			final Vec2i vertexLoc = locInfo.getVertexLoc();
			if( lastLocInfo.isVertexLocValid() && 
					lastLocInfo.getVertexLoc().equals(vertexLoc) ) {
				// Still in vertex, was handled before it is still handled now
				handled = true;
			}
			else {
				// Moving into new vertex, try to handle it
				handled = MultiHandler.canHandleMouseEnter( vertexCallbacks, vertexLoc, event );
			}
		}
		
		if( !handled ) 
			locInfo.invalidateVertexLoc();
		else {
			locInfo.invalidateVerticalEdgeLoc();
			locInfo.invalidateHorizontalEdgeLoc();
			locInfo.invalidateCellLoc();
		}
	}

	/**
	 * Tries to handle a VerticalEdgeEnter event. If the event is successfully handled, then
	 * the function modifies the passed locInfo object to invalidate all other locations, 
	 * making sure no further event will be handled.
	 * 
	 * @param locInfo The locInfo generate from the mouse position
	 * @param event The event that generated this call
	 */
	private void handleVerticalEdgeEnterEvent(LocInfo locInfo, MouseEvent<? extends EventHandler> event) {

		boolean handled = false;
		
		if( verticalEdgeCallbacks != null && locInfo.isVerticalEdgeLocValid() ) {
			final Vec2i edgeLoc = locInfo.getVerticalEdgeLoc();
			if( lastLocInfo.isVerticalEdgeLocValid() && 
					lastLocInfo.getVerticalEdgeLoc().equals(edgeLoc) ) {
				// Still in edge, was handled before it is still handled now
				handled = true;
			}
			else {
				// Moving into new edge, try to handle it
				handled = MultiHandler.canHandleMouseEnter( verticalEdgeCallbacks, edgeLoc, event );
			}
		}
		
		if( !handled ) 
			locInfo.invalidateVerticalEdgeLoc();
		else {
			locInfo.invalidateVertexLoc();
			locInfo.invalidateHorizontalEdgeLoc();
			locInfo.invalidateCellLoc();
		}
	}

	/**
	 * Tries to handle a HorizontalEdgeEnter event. If the event is successfully handled, then
	 * the function modifies the passed locInfo object to invalidate all other locations, 
	 * making sure no further event will be handled.
	 * 
	 * @param locInfo The locInfo generate from the mouse position
	 * @param event The event that generated this call
	 */
	private void handleHorizontalEdgeEnterEvent(LocInfo locInfo, MouseEvent<? extends EventHandler> event) {

		boolean handled = false;
		
		if( horizontalEdgeCallbacks != null && locInfo.isHorizontalEdgeLocValid() ) {
			final Vec2i edgeLoc = locInfo.getHorizontalEdgeLoc();
			if( lastLocInfo.isHorizontalEdgeLocValid() && 
					lastLocInfo.getHorizontalEdgeLoc().equals(edgeLoc) ) {
				// Still in edge, was handled before it is still handled now
				handled = true;
			}
			else {
				// Moving into new edge, try to handle it
				handled = MultiHandler.canHandleMouseEnter( horizontalEdgeCallbacks, edgeLoc, event );
			}
		}
		
		if( !handled ) 
			locInfo.invalidateHorizontalEdgeLoc();
		else {
			locInfo.invalidateVertexLoc();
			locInfo.invalidateVerticalEdgeLoc();
			locInfo.invalidateCellLoc();
		}
	}

	/**
	 * Tries to handle a CellEnter event. If the event is successfully handled, then
	 * the function modifies the passed locInfo object to invalidate all other locations, 
	 * making sure no further event will be handled.
	 * 
	 * @param locInfo The locInfo generate from the mouse position
	 * @param event The event that generated this call
	 */
	private void handleCellEnterEvent(LocInfo locInfo, MouseEvent<? extends EventHandler> event) {

		boolean handled = false;
		
		if( cellCallbacks != null && locInfo.isCellLocValid() ) {
			final Vec2i cellLoc = locInfo.getCellLoc();
			if( lastLocInfo.isCellLocValid() && 
					lastLocInfo.getCellLoc().equals(cellLoc) ) {
				// Still in edge, was handled before it is still handled now
				handled = true;
			}
			else {
				// Moving into new edge, try to handle it
				handled = MultiHandler.canHandleMouseEnter( cellCallbacks, cellLoc, event );
			}
		}
		
		if( !handled ) 
			locInfo.invalidateCellLoc();
		else {
			locInfo.invalidateVertexLoc();
			locInfo.invalidateVerticalEdgeLoc();
			locInfo.invalidateHorizontalEdgeLoc();
		}
	}

	/**
	 * Fills all the location information for a given pixel position. Location information
	 * includes: vertex location (x, y grid coordinates of the vertex), vertical edge location
	 * (x, y grid coordinates of the top vertex), horizontal edge location (x, y grid coordinates
	 * of the left vertex), cell location (x, y grid coordinates of the cell). Some or all of 
	 * these might be marked as invalid if the position is to far from a vertex or edge, or doesn't
	 * fall inside a cell. Vertical edge location and horizontal edge location are mutually exclusive
	 * and will never be valid together.
	 * 
	 * @param pixelPos The position, in pixel, that need to be converted to locations
	 * @return The location information for the given position
	 * @throws Exception
	 */
	private LocInfo fillLocInfo(Vec2i pixelPos) throws Exception {

		LocInfo locInfo = new LocInfo();

		SGView view = getView();
		if( view == null ) return locInfo;
		
		final Vec2i vertexLoc = view.getNearestVertex( pixelPos );
		if( !view.isValidVertex(vertexLoc) ) {
			locInfo.invalidateAll();
			return locInfo;
		}
		final Vec2i vertexPos = view.getVertexPosition( vertexLoc );
		final Vec2i cellLoc = getCellLoc( vertexLoc, vertexPos, pixelPos );
		
		locInfo.setLocations( vertexLoc, cellLoc );
		Vec2i cellSize;
		if( view.isValidCell(cellLoc) ) {
			cellSize = getCellSize( cellLoc );
		}
		else {
			locInfo.invalidateCellLoc();
			cellSize = getCellSize( new Vec2i(0,0) );
		}
		
		
		Vec2i vEdgeLoc = locInfo.getVerticalEdgeLoc();
		Vec2i hEdgeLoc = locInfo.getHorizontalEdgeLoc();
		if( !view.isValidVerticalEdge(vEdgeLoc))
			locInfo.invalidateVerticalEdgeLoc();
		if( !view.isValidHorizontalEdge(hEdgeLoc))
			locInfo.invalidateHorizontalEdgeLoc();
		
		final Vec2i distToVertex = new Vec2i( 
				Math.abs(pixelPos.x - vertexPos.x),
				Math.abs(pixelPos.y - vertexPos.y) );
		
		if( !closeEnoughToVertex(distToVertex, cellSize) ) 
			locInfo.invalidateVertexLoc();
		if( !closeEnoughToVerticalEdge(distToVertex, cellSize) ) 
			locInfo.invalidateVerticalEdgeLoc();
		if( !closeEnoughToHorizontalEdge(distToVertex, cellSize) ) 
			locInfo.invalidateHorizontalEdgeLoc();
		if( locInfo.isVerticalEdgeLocValid() && locInfo.isHorizontalEdgeLocValid() ) {
			if( distToVertex.x < distToVertex.y )
				locInfo.invalidateHorizontalEdgeLoc();
			else
				locInfo.invalidateVerticalEdgeLoc();
		}
		
		return locInfo;
	}

	private boolean closeEnoughToVertex(Vec2i distToVertex, Vec2i cellSize) {
		return (distToVertex.x <= Math.ceil(cellSize.x * relativeVertexSize)) &&
			(distToVertex.y <= Math.ceil(cellSize.y * relativeVertexSize));
	}

	private boolean closeEnoughToVerticalEdge(Vec2i distToVertex, Vec2i cellSize) {
		return distToVertex.x <= Math.ceil(cellSize.x * relativeEdgeSize);
	}

	private boolean closeEnoughToHorizontalEdge(Vec2i distToVertex,	Vec2i cellSize) {
		return distToVertex.y <= Math.ceil(cellSize.y * relativeEdgeSize);
	}

	/**
	 * Returns the size of a given cell, in pixels
	 * 
	 * @param cellLoc Location of the cell (x, y)
	 * @return Size of the cell, in pixels
	 * @throws Exception 
	 */
	private Vec2i getCellSize(Vec2i cellLoc) throws Exception {
		SGView view = getView();
		Recti rect = view.getCellPositionAndSize(cellLoc);
		return new Vec2i( rect.w, rect.h );
	}

	/**
	 * Finds the location of the cell where the mouse is
	 * 
	 * @param vertexLoc Location of the nearest vertex
	 * @param vertexPos Position, in pixel, of the nearest vertex
	 * @param mousePos  Position, in pixel, of the mouse (in the same reference frame as vertexPos)
	 * @return
	 */
	private Vec2i getCellLoc(Vec2i vertexLoc, Vec2i vertexPos, Vec2i mousePos) {
		Vec2i result = new Vec2i( vertexLoc );
		if( mousePos.x < vertexPos.x ) result.x--;
		if( mousePos.y < vertexPos.y ) result.y--;
		return result;			
	}

	/**
	 * Adds an object responsible of controlling cell-related mouse events
	 * 
	 * @param callback Object for mouse event callback
	 */
	public void addCellCallback(ComponentCallback callback) {
		cellCallbacks.add( callback );
	}
	
	/**
	 * Adds an object responsible of controlling vertical-edge-related mouse events
	 * 
	 * @param callback Object for mouse event callback
	 */
	public void addVerticalEdgeCallback(ComponentCallback callback ) {
		verticalEdgeCallbacks.add( callback );
	}

	/**
	 * Adds an object responsible of controlling horizontal-edge-related mouse events
	 * 
	 * @param callback Object for mouse event callback
	 */
	public void addHorizontalEdgeCallback(ComponentCallback callback) {
		horizontalEdgeCallbacks.add( callback );
	}	
	
	/**
	 * Adds an object responsible of controlling vertex-related mouse events
	 * 
	 * @param callback Object for mouse event callback
	 */
	public void addVertexCallback(ComponentCallback callback) {
		vertexCallbacks.add( callback );
	}


	/**
	 * Deletes an object responsible of controlling cell-related mouse events
	 * 
	 * @param callback Object for mouse event callback
	 */
	public void removeCellCallback(ComponentCallback callback) {
		cellCallbacks.remove( callback );
	}
	
	/**
	 * Deletes an object responsible of controlling vertical-edge-related mouse events
	 * 
	 * @param callback Object for mouse event callback
	 */
	public void removeVerticalEdgeCallback(ComponentCallback callback ) {
		verticalEdgeCallbacks.remove( callback );
	}

	/**
	 * Deletes an object responsible of controlling horizontal-edge-related mouse events
	 * 
	 * @param callback Object for mouse event callback
	 */
	public void removeHorizontalEdgeCallback(ComponentCallback callback) {
		horizontalEdgeCallbacks.remove( callback );
	}	
	
	/**
	 * Deletes an object responsible of controlling vertex-related mouse events
	 * 
	 * @param callback Object for mouse event callback
	 */
	public void removeVertexCallback(ComponentCallback callback) {
		vertexCallbacks.remove( callback );
	}
	
	/**
	 * Adds a standard mouse down handler to the decorator
	 * 
	 * @param ControlPanelHandler The handler to add
	 * @return The handler registration to use to remove handler
	 */
	public HandlerRegistration addMouseDownHandler(MouseDownHandler handler) {
		return panel.addMouseDownHandler( handler );
	}
	
	/**
	 * Adds a standard global mouse up handler to the decorator
	 * 
	 * @param ControlPanelHandler The handler to add
	 * @return The handler registration to use to remove handler
	 */
	public HandlerRegistration addMouseUpHandler(MouseUpHandler handler) {
		return panel.addMouseUpHandler( handler );
	}
	
	/**
	 * Adds a standard global mouse move handler to the decorator
	 * 
	 * @param ControlPanelHandler The handler to add
	 * @return The handler registration to use to remove handler
	 */
	public HandlerRegistration addMouseMoveHandler(MouseMoveHandler handler) {
		return panel.addMouseMoveHandler( handler );
	}
	
	/**
	 * Adds a standard global mouse out handler to the decorator
	 * 
	 * @param ControlPanelHandler The handler to add
	 * @return The handler registration to use to remove handler
	 */
	public HandlerRegistration addMouseOutHandler(MouseOutHandler handler) {
		return panel.addMouseOutHandler( handler );
	}
	
	/**
	 * Sets the relative size of the vertex with respect to a cell. For example, a value of 0.1
	 * means the vertex size is 10% of the total cell size
	 * 
	 * @param relativeVertexSize The relative vertex size
	 */
	public void setRelativeVertexSize(float relativeVertexSize) {
		this.relativeVertexSize = relativeVertexSize;
	}

	/**
	 * Sets the relative size of the edge with respect to a cell. For example, a value of 0.1
	 * means the edge size is 10% of the total cell size
	 * 
	 * @param relativeEdgeSize The relative edge size
	 */
	public void setRelativeEdgeSize(float relativeEdgeSize) {
		this.relativeEdgeSize = relativeEdgeSize;
	}

	@Override
	protected void notifyCellChanged() {
		// TODO This might warrant a fake mouse move event
		// Maybe we should build our own mouse event so that we can keep it and refire it?
	}

	
}
